<!DOCTYPE html>
<html lang="en">
<head>
  <title>Middleware - Born to build better enterprise frameworks and apps</title>
  <meta charset="utf-8">
  <meta name="description" content="index.description">
  <meta http-equiv="X-UA-Compatible" content="IE=Edge,chrome=1">
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
  <link rel="icon" href="/images/favicon.png" type="image/x-icon">
  <link rel="stylesheet" href="https://cdn.jsdelivr.net/docsearch.js/2/docsearch.min.css" />
<link rel="stylesheet" href="/css/index.css">

    <script>
    !function(t,e,a,r,c){t.TracertCmdCache=t.TracertCmdCache||[],t[c]=window[c]||
      {_isInit:!0,call:function(){t.TracertCmdCache.push(arguments)},
      start:function(t){this.call('start',t)}},t[c].l=new Date;
      var n=e.createElement(a),s=e.getElementsByTagName(a)[0];
      n.async=!0,n.src=r,s.parentNode.insertBefore(n,s)}
    (window,document,'script','https://tracert.alipay.com/tracert.js','Tracert');
      Tracert.start({
        plugins: [ 'BucName' ],
        spmAPos: 'a454',
        spmBPos: 'b4893',
      });
    </script>
  
</head>
<body>
  <div class="nav" >
  <header>
    <a href="/en/" class="nav-logo leftpadding" alt="egg"><img src="https://zos.alipayobjects.com/rmsportal/VTcUYAaoKqXyHJbLAPyF.svg"></a>
    <ul class="nav-item">
      <li>
        <form id="search-form">
          <input type="text" id="search-query" class="search-query st-default-search-input">
        </form>
      </li>
      <li><a href="/en/intro/" alt="Guide">Guide</a></li><li><a href="/api/" alt="API">API</a></li><li><a href="/en/tutorials/index.html" alt="Tutorials">Tutorials</a></li><li><a href="https://github.com/search?q=topic%3Aegg-plugin&type=Repositories" alt="Plugins">Plugins</a></li><li><a href="https://github.com/eggjs/egg/releases" alt="Release">Release</a></li>
      
      
        <li class="translations">
          <a class="nav-link">中文文档</a>
          <span class="arrow"></span><ul id="dropdownContent" class="dropdown-content"><li><a id="en" href="/en/basics/middleware.html" style="color: #22ab28">English</a></li><li><a id="zh-cn" href="/zh-cn/basics/middleware.html" >中文</a></li></ul>
        </li>
      
      <li><iframe src="https://ghbtns.com/github-btn.html?user=eggjs&repo=egg&type=star&count=true" frameborder="0" scrolling="0" width="150px" height="20px"></iframe></li>
    </ul>
    <a id="mobileTrigger" href="#" class="mobile-trigger">
      <ul>
        <li></li>
        <li></li>
        <li></li>
      </ul>
    </a>
  </header>
</div>
  <div id="container" class="container">
    <div class="page-main">
  <article class="markdown-body">
    <h1>Middleware</h1>
    <p>In <a href="../intro/egg-and-koa.html">the previous chapter</a>, we say that Egg is based on Koa, so the form of middleware in Egg is the same as in Koa, i.e. they are both based on <a href="../intro/egg-and-koa.html#midlleware">the onion model</a>.</p>
<h2 id="writing-middleware"><a class="markdown-anchor" href="#writing-middleware">#</a> Writing Middleware</h2>
<h3 id="how-to-write"><a class="markdown-anchor" href="#how-to-write">#</a> How to write</h3>
<p>Let's take a look at how to write a middleware from a simple gzip example.</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> isJSON = <span class="built_in">require</span>(<span class="string">'koa-is-json'</span>);</span><br><span class="line"><span class="keyword">const</span> zlib = <span class="built_in">require</span>(<span class="string">'zlib'</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">async</span> <span class="function"><span class="keyword">function</span> <span class="title">gzip</span>(<span class="params">ctx, next</span>) </span>&#123;</span><br><span class="line">  <span class="keyword">await</span> next();</span><br><span class="line"></span><br><span class="line">  <span class="comment">// convert the response body to gzip after the completion of the execution of subsequent middleware</span></span><br><span class="line">  <span class="keyword">let</span> body = ctx.body;</span><br><span class="line">  <span class="keyword">if</span> (!body) <span class="keyword">return</span>;</span><br><span class="line">  <span class="keyword">if</span> (isJSON(body)) body = <span class="built_in">JSON</span>.stringify(body);</span><br><span class="line"></span><br><span class="line">  <span class="comment">// set gzip body, correct the response header</span></span><br><span class="line">  <span class="keyword">const</span> stream = zlib.createGzip();</span><br><span class="line">  stream.end(body);</span><br><span class="line">  ctx.body = stream;</span><br><span class="line">  ctx.set(<span class="string">'Content-Encoding'</span>, <span class="string">'gzip'</span>);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<p>You might find that the middleware's style in the framework is exactly the same as in Koa, yes,  any middleware in Koa can be used directly by the framework.</p>
<h3 id="configuration"><a class="markdown-anchor" href="#configuration">#</a> Configuration</h3>
<p>Usually the middleware has its own configuration. In the framework, a complete middleware includes the configuration process. A middleware is a single file placed under <code>app/middleware</code> directory, which needs to exports a function that accepts two parameters:</p>
<ul>
<li>options: the configuration field of the middleware, <code>app.config[${middlewareName}]</code> will be passed in by the framework</li>
<li>app: the Application instance of current application</li>
</ul>
<p>We will do a simple optimization to the gzip middleware above, making it do gzip compression only if the body size is greater than a configured threshold. So, we need to create a new file <code>gzip.js</code> in <code>app/middleware</code> directory.</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// app/middleware/gzip.js</span></span><br><span class="line"><span class="keyword">const</span> isJSON = <span class="built_in">require</span>(<span class="string">'koa-is-json'</span>);</span><br><span class="line"><span class="keyword">const</span> zlib = <span class="built_in">require</span>(<span class="string">'zlib'</span>);</span><br><span class="line"></span><br><span class="line"><span class="built_in">module</span>.exports = <span class="function"><span class="params">options</span> =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="keyword">async</span> <span class="function"><span class="keyword">function</span> <span class="title">gzip</span>(<span class="params">ctx, next</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">await</span> next();</span><br><span class="line"></span><br><span class="line">    <span class="comment">// convert the response body to gzip after the completion of the execution of subsequent middleware</span></span><br><span class="line">    <span class="keyword">let</span> body = ctx.body;</span><br><span class="line">    <span class="keyword">if</span> (!body) <span class="keyword">return</span>;</span><br><span class="line"></span><br><span class="line">    <span class="comment">// support options.threshold</span></span><br><span class="line">    <span class="keyword">if</span> (options.threshold &amp;&amp; ctx.length &lt; options.threshold) <span class="keyword">return</span>;</span><br><span class="line"></span><br><span class="line">    <span class="keyword">if</span> (isJSON(body)) body = <span class="built_in">JSON</span>.stringify(body);</span><br><span class="line"></span><br><span class="line">    <span class="comment">// set gzip body, correct the response header</span></span><br><span class="line">    <span class="keyword">const</span> stream = zlib.createGzip();</span><br><span class="line">    stream.end(body);</span><br><span class="line">    ctx.body = stream;</span><br><span class="line">    ctx.set(<span class="string">'Content-Encoding'</span>, <span class="string">'gzip'</span>);</span><br><span class="line">  &#125;;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<h2 id="using-middleware"><a class="markdown-anchor" href="#using-middleware">#</a> Using Middleware</h2>
<p>After writing middleware, we also need to mount it, there are following ways to support:</p>
<h3 id="using-middleware-in-application"><a class="markdown-anchor" href="#using-middleware-in-application">#</a> Using Middleware in Application</h3>
<p>We can load customized middleware completely by configuration in the application, and decide their order.
If we need to load the gzip middleware in the above,
we can edit <code>config.default.js</code> like this:</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="built_in">module</span>.exports = &#123;</span><br><span class="line">  <span class="comment">// configure the middleware you need, which loads in the order of array</span></span><br><span class="line">  middleware: [ <span class="string">'gzip'</span> ],</span><br><span class="line"></span><br><span class="line">  <span class="comment">// configure the gzip middleware</span></span><br><span class="line">  gzip: &#123;</span><br><span class="line">    threshold: <span class="number">1024</span>, <span class="comment">// skip response body which size is less than 1K</span></span><br><span class="line">  &#125;,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<p>This config will be merged to <code>app.config.appMiddleware</code> at starting up.</p>
<h2 id="using-middleware-in-framework-and-plugin"><a class="markdown-anchor" href="#using-middleware-in-framework-and-plugin">#</a> Using Middleware in Framework and Plugin</h2>
<p>Framework and Plugin don't support to configure <code>middleware</code> in <code>config.default.js</code>, you should mount it in <code>app.js</code>:</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// app.js</span></span><br><span class="line"><span class="built_in">module</span>.exports = <span class="function"><span class="params">app</span> =&gt;</span> &#123;</span><br><span class="line">  <span class="comment">// put to the first place to count request cost</span></span><br><span class="line">  app.config.coreMiddleware.unshift(<span class="string">'report'</span>);</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">// app/middleware/report.js</span></span><br><span class="line"><span class="built_in">module</span>.exports = <span class="function"><span class="params">()</span> =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">return</span> <span class="keyword">async</span> <span class="function"><span class="keyword">function</span> (<span class="params">ctx, next</span>) </span>&#123;</span><br><span class="line">    <span class="keyword">const</span> startTime = <span class="built_in">Date</span>.now();</span><br><span class="line">    <span class="keyword">await</span> next();</span><br><span class="line">    reportTime(<span class="built_in">Date</span>.now() - startTime);</span><br><span class="line">  &#125;</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<p>Middlewares which are defined at Application (<code>app.config.appMiddleware</code>) and Framework(<code>app.config.coreMiddleware</code>) will be merged to <code>app.middleware</code> by loader at staring up.</p>
<h2 id="using-middleware-in-router"><a class="markdown-anchor" href="#using-middleware-in-router">#</a> Using Middleware in Router</h2>
<p>The middleware configured in the above ways is global, and it will process every request.</p>
<p>If you do want to take effect only for single route, you could just instantiate and mount it at <code>app/router.js</code>:</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="built_in">module</span>.exports = <span class="function"><span class="params">app</span> =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">const</span> gzip = app.middleware.gzip(&#123; <span class="attr">threshold</span>: <span class="number">1024</span> &#125;);</span><br><span class="line">  app.router.get(<span class="string">'/needgzip'</span>, gzip, app.controller.handler);</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<h2 id="default-framework-middleware"><a class="markdown-anchor" href="#default-framework-middleware">#</a> Default Framework Middleware</h2>
<p>In addition to application layer loading middleware, the framework itself and other plugins will also load many middlewares. All the config fields of these built-in middlewares can be modified by modifying the ones with the same name in the config file, for example <a href="https://github.com/eggjs/egg/tree/master/app/middleware" target="_blank" rel="noopener">Framework Built-in Plugin</a> uses a bodyParser middleware(the framework loader will change the various delimiters in the file name into the camel style), and we can add configs below in <code>config/config.default.js</code> to modify the bodyParser:</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="built_in">module</span>.exports = &#123;</span><br><span class="line">  bodyParser: &#123;</span><br><span class="line">    jsonLimit: <span class="string">'10m'</span>,</span><br><span class="line">  &#125;,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<p>** Note: middleware loaded by the framework and plugins are loaded earlier than those loaded by the application layer, and the application-layer middleware cannot overwrite the default framework middleware. If the application layer loads customized middleware that has the same name with default framework middleware, an error will be reported on starting up. **</p>
<h2 id="use-koas-middleware"><a class="markdown-anchor" href="#use-koas-middleware">#</a> Use Koa's Middleware</h2>
<p>Developer is free to use Koa Middleware, all middlewares used in Koa can be directly used in the framework too.</p>
<p>For example, Koa uses <a href="https://github.com/koajs/compress" target="_blank" rel="noopener">koa-compress</a> in this way:</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="keyword">const</span> koa = <span class="built_in">require</span>(<span class="string">'koa'</span>);</span><br><span class="line"><span class="keyword">const</span> compress = <span class="built_in">require</span>(<span class="string">'koa-compress'</span>);</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> app = koa();</span><br><span class="line"></span><br><span class="line"><span class="keyword">const</span> options = &#123; <span class="attr">threshold</span>: <span class="number">2048</span> &#125;;</span><br><span class="line">app.use(compress(options));</span><br></pre></td></tr></table></figure>
<p>We can load the middleware according to the framework specification like this:</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// app/middleware/compress.js</span></span><br><span class="line"><span class="comment">// interfaces(`(options) =&gt; middleware`) exposed by koa-compress match the framework middleware requirements</span></span><br><span class="line"><span class="built_in">module</span>.exports = <span class="built_in">require</span>(<span class="string">'koa-compress'</span>);</span><br></pre></td></tr></table></figure>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// config/config.default.js</span></span><br><span class="line"><span class="built_in">module</span>.exports = &#123;</span><br><span class="line">  middleware: [ <span class="string">'compress'</span> ],</span><br><span class="line">  compress: &#123;</span><br><span class="line">    threshold: <span class="number">2048</span>,</span><br><span class="line">  &#125;,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<p>If the third-party Koa middleware do not follow the rule, then you can wrap it yourself:</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="comment">// config/config.default.js</span></span><br><span class="line"><span class="built_in">module</span>.exports = &#123;</span><br><span class="line">  webpack: &#123;</span><br><span class="line">    compiler: &#123;&#125;,</span><br><span class="line">    others: &#123;&#125;,</span><br><span class="line">  &#125;,</span><br><span class="line">&#125;;</span><br><span class="line"></span><br><span class="line"><span class="comment">// app/middleware/webpack.js</span></span><br><span class="line"><span class="keyword">const</span> webpackMiddleware = <span class="built_in">require</span>(<span class="string">'some-koa-middleware'</span>);</span><br><span class="line"></span><br><span class="line"><span class="built_in">module</span>.exports = <span class="function">(<span class="params">options, app</span>) =&gt;</span> &#123;</span><br><span class="line">  <span class="keyword">return</span> webpackMiddleware(options.compiler, options.others);</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure>
<h2 id="general-configuration"><a class="markdown-anchor" href="#general-configuration">#</a> General Configuration</h2>
<p>These general config fields are supported by middleware loaded by the application layer and built in by the framework:</p>
<ul>
<li>enable: enable the middleware or not</li>
<li>match: set some rules with which only the request matches can go through this middleware</li>
<li>ignore: set some rules with which the request matches can't go through this middleware</li>
</ul>
<h3 id="enable"><a class="markdown-anchor" href="#enable">#</a> enable</h3>
<p>If our application does not need default bodyParser to resolve the request body, we can configure enable for false to close it.</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="built_in">module</span>.exports = &#123;</span><br><span class="line">  bodyParser: &#123;</span><br><span class="line">    enable: <span class="literal">false</span>,</span><br><span class="line">  &#125;,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<h3 id="match-and-ignore"><a class="markdown-anchor" href="#match-and-ignore">#</a> match and ignore</h3>
<p>match and ignore share the same parameter but do the opposite things. match and ignore cannot be configured in the same time.</p>
<p>If we want gzip to be used only by url requests starting with <code>/static</code>, the match config field can be set like this:</p>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="built_in">module</span>.exports = &#123;</span><br><span class="line">  gzip: &#123;</span><br><span class="line">    match: <span class="string">'/static'</span>,</span><br><span class="line">  &#125;,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>
<p>match and ignore support various types of configuration ways:</p>
<ol>
<li>String: when string, it sets the prefix of a url path, and all urls starting with this prefix will be matched.</li>
<li>Regular expression: when regular expression, all urls satisfy this regular expression will be matched.</li>
<li>Function: when function, the request context will be passed to it and what it returns(true/false) determines whether the request is matched or not.</li>
</ol>
<figure class="highlight js"><table><tr><td class="code"><pre><span class="line"><span class="built_in">module</span>.exports = &#123;</span><br><span class="line">  gzip: &#123;</span><br><span class="line">    match(ctx) &#123;</span><br><span class="line">      <span class="comment">// enabled on ios devices</span></span><br><span class="line">      <span class="keyword">const</span> reg = <span class="regexp">/iphone|ipad|ipod/i</span>;</span><br><span class="line">      <span class="keyword">return</span> reg.test(ctx.get(<span class="string">'user-agent'</span>));</span><br><span class="line">    &#125;,</span><br><span class="line">  &#125;,</span><br><span class="line">&#125;;</span><br></pre></td></tr></table></figure>

  </article>
  <aside id="mobileAside" class="toc">
  <div class="mobile-menu">
    <ul>
      <li><a href="/en/intro/" alt="Guide">Guide</a></li><li><a href="/api/" alt="API">API</a></li><li><a href="/en/tutorials/index.html" alt="Tutorials">Tutorials</a></li><li><a href="https://github.com/search?q=topic%3Aegg-plugin&type=Repositories" alt="Plugins">Plugins</a></li><li><a href="https://github.com/eggjs/egg/releases" alt="Release">Release</a></li>
      
      
        <li class="translations">
          <a class="nav-link">中文文档</a>
          <span class="arrow"></span><ul id="dropdownContent" class="dropdown-content"><li><a id="en" href="/en/basics/middleware.html" style="color: #22ab28">English</a></li><li><a id="zh-cn" href="/zh-cn/basics/middleware.html" >中文</a></li></ul>
        </li>
      
    </ul>
  </div>
  <dl><dt id="title-Intro" style="cursor: pointer;" class="aside-title">Guide<a id="collapse-icon-Intro" class="icon opend"></a></dt><dd id=panel-Intro><ul><li><a href="/en/intro/index.html" class="menu-link">What is Egg?</a></li><li><a href="/en/intro/egg-and-koa.html" class="menu-link">Egg and Koa</a></li><li><a href="/en/intro/quickstart.html" class="menu-link">Quick Start</a></li><li><a href="/en/tutorials/progressive.html" class="menu-link">Progressive</a></li><li><a href="/en/migration.html" class="menu-link">Migration to 2.x</a></li></ul></dd><dt id="title-Basics" style="cursor: pointer;" class="aside-title">Basis Function<a id="collapse-icon-Basics" class="icon opend"></a></dt><dd id=panel-Basics><ul><li><a href="/en/basics/structure.html" class="menu-link">Directory Structure</a></li><li><a href="/en/basics/objects.html" class="menu-link">Built-in Objects</a></li><li><a href="/en/basics/env.html" class="menu-link">Environment</a></li><li><a href="/en/basics/config.html" class="menu-link">Configuration</a></li><li><a href="/en/basics/middleware.html" class="menu-link">Middleware</a></li><li><a href="/en/basics/router.html" class="menu-link">Router</a></li><li><a href="/en/basics/controller.html" class="menu-link">Controller</a></li><li><a href="/en/basics/service.html" class="menu-link">Service</a></li><li><a href="/en/basics/plugin.html" class="menu-link">Plugin</a></li><li><a href="/en/basics/schedule.html" class="menu-link">Schedule</a></li><li><a href="/en/basics/extend.html" class="menu-link">Extend</a></li><li><a href="/en/basics/app-start.html" class="menu-link">Custom Init</a></li></ul></dd><dt id="title-Core" style="cursor: pointer;" class="aside-title">Core<a id="collapse-icon-Core" class="icon opend"></a></dt><dd id=panel-Core><ul><li><a href="/en/core/development.html" class="menu-link">Development</a></li><li><a href="/en/core/unittest.html" class="menu-link">Unit Testing</a></li><li><a href="/en/core/deployment.html" class="menu-link">Deployment</a></li><li><a href="/en/core/logger.html" class="menu-link">Logger</a></li><li><a href="/en/core/httpclient.html" class="menu-link">HttpClient</a></li><li><a href="/en/core/cookie-and-session.html" class="menu-link">Cookie and Session</a></li><li><a href="/en/core/cluster-and-ipc.html" class="menu-link">Cluster and IPC</a></li><li><a href="/en/core/view.html" class="menu-link">View</a></li><li><a href="/en/core/error-handling.html" class="menu-link">Error Handling</a></li><li><a href="/en/core/security.html" class="menu-link">Security</a></li><li><a href="/en/core/i18n.html" class="menu-link">i18n</a></li></ul></dd><dt id="title-Tutorials" style="cursor: pointer;" class="aside-title">Tutorials<a id="collapse-icon-Tutorials" class="icon opend"></a></dt><dd id=panel-Tutorials><ul><li><a href="/en/tutorials/mysql.html" class="menu-link">MySQL</a></li><li><a href="/en/tutorials/restful.html" class="menu-link">RESTful API</a></li><li><a href="/en/tutorials/passport.html" class="menu-link">Passport</a></li><li><a href="/en/tutorials/socketio.html" class="menu-link">Socket.IO</a></li><li><a href="/en/tutorials/assets.html" class="menu-link">Assets</a></li><li><a href="/en/tutorials/typescript.html" class="menu-link">TypeScript</a></li></ul></dd><dt id="title-Advanced" style="cursor: pointer;" class="aside-title">Advanced<a id="collapse-icon-Advanced" class="icon opend"></a></dt><dd id=panel-Advanced><ul><li><a href="/en/advanced/loader.html" class="menu-link">Loader</a></li><li><a href="/en/advanced/plugin.html" class="menu-link">Plugin Development</a></li><li><a href="/en/advanced/framework.html" class="menu-link">Framework</a></li><li><a href="/en/advanced/cluster-client.html" class="menu-link">Cluster Enhancement</a></li><li><a href="/en/advanced/view-plugin.html" class="menu-link">View Plugin</a></li><li><a href="/en/style-guide.html" class="menu-link">Style Guide</a></li></ul></dd><dt id="title-Community" style="cursor: pointer;" class="aside-title">Community<a id="collapse-icon-Community" class="icon opend"></a></dt><dd id=panel-Community><ul><li><a href="/en/plugins/" class="menu-link">Plugin List</a></li><li><a href="/en/contributing.html" class="menu-link">Contributing</a></li><li><a href="/en/resource.html" class="menu-link">Resource</a></li><li><a href="/en/faq.html" class="menu-link">FAQ</a></li></ul></dd></dl>
</aside>
<script>
var mobileTrigger = document.getElementById('mobileTrigger');
var mobileAside = document.getElementById('mobileAside');

var expandMenu = function(title) {
  // handle icon
  const collapseIcon = document.getElementById('collapse-icon-' + title);
  if (collapseIcon) {
    collapseIcon.className = 'icon opend';
  }
  // handle panelEle
  const panelEle = document.getElementById('panel-' + title);
  if (panelEle) {
    panelEle.className = '';
  }
}

var collapseMenu = function(title) {
  // handle icon
  const collapseIcon = document.getElementById('collapse-icon-' + title);
  if (collapseIcon) {
    collapseIcon.className = 'icon closed';
  }
  // handle panelEle
  const panelEle = document.getElementById('panel-' + title);
  if (panelEle) {
    panelEle.className = 'aside-panel-hidden';
  }
}

mobileAside.onclick = function(e) {
  const targetId = e.target.id;
  if (targetId && (targetId.indexOf('title-') > -1 || targetId.indexOf('collapse-icon-') > -1)) {
    const title = targetId.replace('title-', '').replace('collapse-icon-', '');
    try { 
      // the the browser may have no localStroage or JSON.parse may throw exception.
      const menuInfo = JSON.parse(window.localStorage.getItem('menuInfo'));
        
      // current menu status
      const curClosed = menuInfo[title] ? menuInfo[title].closed : false; // default false

      // change UI
      curClosed ? expandMenu(title) : collapseMenu(title);

      // save menuInfo to localStorage
      menuInfo[title] = { closed: !curClosed } // opposite
      window.localStorage.setItem('menuInfo', JSON.stringify(menuInfo));
    } catch (e) {}
  }
};

mobileTrigger.onclick = function(e) {
  e.preventDefault();
  if (mobileAside.className.indexOf('mobile-show') === -1) {
    mobileAside.className += ' mobile-show';
  } else {
    mobileAside.className = 'toc';
  }
};

(function() {
  // save data to localStorage because the page will refresh when user change the url.
  let menuInfo;
  try { 
    // the the browser may have no localStroage or JSON.parse may throw exception.
    menuInfo = JSON.parse(window.localStorage.getItem('menuInfo'));
    if (!menuInfo) {
      menuInfo = {};
      window.localStorage.setItem('menuInfo', JSON.stringify(menuInfo));
    }
  } catch (e) {
    menuInfo = {}; // default {}
  }

  for (const title in menuInfo) {
    if (menuInfo[title] && menuInfo[title].closed) { // menu in closed status.
      collapseMenu(title);
    } else {
      expandMenu(title);
    }
  }

  // highlight menu
  const pathname = window.location.pathname;
  const selector = `a[href="${pathname}"].menu-link,a[href="${pathname}index.html"].menu-link`;
  const menuItem = mobileAside.querySelector(selector);
  if (menuItem) { menuItem.className += ' highlight'; }
})();
</script>

</div>

  </div>
</body>
<script src="https://cdn.jsdelivr.net/docsearch.js/2/docsearch.min.js"></script>
<script>
docsearch({
  apiKey: '1561de31a86f79507ea00cdb54ce647c',
  indexName: 'eggjs',
  inputSelector: '#search-query',
});
</script>
<div class="cnzz">
<script src="https://s11.cnzz.com/z_stat.php?id=1261142226&web_id=1261142226" language="JavaScript"></script>
</div>

</html>
